var layerFields = [{name : 'name',sortType : Ext.data.SortTypes.asUCString},'getMap','thumb','bg','w','h','abstract','okDates','style','styles','color','bbox','visible','loaded','legend'];
var timelineColors = [
   '#66C2A5'
  ,'#FC8D62'
  ,'#8DA0CB'
  ,'#E78AC3'
  ,'#A6D854'
  ,'#FFD92F'
  ,'#E5C494'
  ,'#B3B3B3'
];
var timelineColorMapping = {};
var timelineColorCounter = 0;
var timelinePlot;
var timelinePlotInit = false;
var originalStyles   = {};
var mapDate          = new Date();
var map;
var proj3857   = new OpenLayers.Projection("EPSG:3857");
var proj4326   = new OpenLayers.Projection("EPSG:4326");

function init() {
  Ext.QuickTips.init();

  var layersGridPanelSM = new Ext.grid.CheckboxSelectionModel({
    singleSelect : true
  });

  new Ext.Viewport({
     layout : 'border'
    ,items  : [
      {
         layout : 'anchor'
        ,region : 'north'
        ,height : 400
        ,title  : 'Data sources'
        ,split       : true
        ,collapsible : true
        ,defaults    : {border : false}
        ,items       : [
          {
             autoHeight : true
            ,cls        : 'directionsJustify'
            ,id         : 'directions'
            ,html       : 'Once you provide a WMS GetCapabilities link and press ENTER above, a list of available WMS layers will appear.  Click on the \'Add to map\' link to explore layers on the interactive map.  If the GetCapabilities query finds any recognized time capabillities (such as a min/max time range or a specific list of times) for a given layer, a timeline will appear to the left of the map.  You may click on it and/or use the map date and time inputs below the map to control the map time.'
          }
          ,new Ext.grid.GridPanel({
            store : new Ext.data.ArrayStore({
               fields   : layerFields
              ,sortInfo : {field : 'name',direction : 'ASC'}
            })
            ,columns : [
              {id : 'thumb',width : 140,align : 'center',renderer : function(val,metadata,rec) {
                Ext.defer(function() {
                  var el = document.getElementById(rec.get('name'));
                  if (el) {
                    var img = new Image();
                    img.onload = function() {
                      el.src = img.src; 
                      el.style.visibility = 'visible';
                      var spinner = document.getElementById(rec.get('name') + '.spinner');
                      if (spinner) {
                        spinner.parentNode.removeChild(spinner);
                      }
                    }
                    img.onerror = function() {
                      el.src = "img/blank.png";
                      el.style.background = 'url(img/error.png) no-repeat center';
                      el.style.visibility = 'visible';
                      var spinner = document.getElementById(rec.get('name') + '.spinner');
                      if (spinner) {
                        spinner.parentNode.removeChild(spinner);
                      }
                    }
                    img.src = rec.get('thumb');
                  }
                },100);
                return '<span style="width:' + rec.get('w') + ';height:' + rec.get('h') + '"><img id="' + rec.get('name') + '.spinner" src="img/spinner.gif"><img style="border:1px solid #d0d0d0;visibility:hidden;width:' + rec.get('w') + ';height:' + rec.get('h') + ';background:url(' + rec.get('bg') + ')" id="' + rec.get('name') + '" src="img/blank.png"></span><br><a href=\'javascript:addToMap("' + rec.get('name') + '")\'>Add to map</a>';
              }}
              ,{id : 'name' ,dataIndex : 'name',renderer : function(val,metadata,rec) {
                var name = val.split('|');
                name.pop();
                return '<p><b>' + name.join('|') + '</b></p>' + '<p>' + rec.get('abstract') + '</p>';
              }}
            ]
            ,anchor           : '100% -0'
            ,hideHeaders      : true
            ,id               : 'getCapsGridPanel'
            ,autoExpandColumn : 'name'
            ,enableHdMenu     : false
            ,loadMask         : true
            ,disableSelection : true
          })
        ]
        ,tbar             : [
          new Ext.form.ComboBox({
            store          : new Ext.data.ArrayStore({
               fields : ['url']
              ,data   : [
                 ['http://ec2-107-21-136-52.compute-1.amazonaws.com:8080/wms/NAVY_HYCOM/?request=getcapabilities&version=1.1.1&service=wms']
                ,['http://64.9.200.113:8080/thredds/wms/SST/LakeErieSST-Agg?service=WMS&version=1.3.0&request=GetCapabilities']
                ,['http://64.9.200.113:8080/thredds/wms/SST/LakeHuronSST-Agg?service=WMS&version=1.3.0&request=GetCapabilities']
                ,['http://wms.testbed.sura.org/wms/in_usf_fvcom_ike_ultralite_vardrag_nowave_2d/?service=WMS&version=1.3.0&request=GetCapabilities']
              ]
            })
            ,width          : 250
            ,triggerAction  : 'all'
            ,emptyText      : 'Provide a WMS GetCapabilities link or select a value for demonstration.'
            ,selectOnFocus  : true
            ,mode           : 'local'
            ,displayField   : 'url'
            ,valueField     : 'url'
            ,id             : 'gcUrl'
            ,listeners  : {
              select : function(cb,rec,i) {
                if (new RegExp(/service=wms/i).test(Ext.getCmp('gcUrl').getValue())) {
                  wmsGetCaps(Ext.getCmp('gcUrl').getValue());
                }
                else {
                  sosGetCaps(Ext.getCmp('gcUrl').getValue());
                }
              }
              ,specialkey : function(but,e) {
                if (e.keyCode == e.ENTER && Ext.getCmp('gcUrl').getRawValue() != '') {
                  Ext.getCmp('getCapsGridPanel').getStore().removeAll();
                  if (new RegExp(/service=wms/i).test(Ext.getCmp('gcUrl').getRawValue())) {
                    wmsGetCaps(Ext.getCmp('gcUrl').getRawValue());
                  }
                  else {
                    sosGetCaps(Ext.getCmp('gcUrl').getRawValue());
                  }
                }
              }
            }
          })
          ,'->'
          ,{
             text    : 'Clear'
            ,handler : function() {
              Ext.getCmp('getCapsGridPanel').getStore().removeAll();
              Ext.getCmp('gcUrl').reset();
            }
          }
          ,' '
        ]
        ,listeners        : {
          bodyresize : function(p,w,h) {
            var el = Ext.getCmp('gcUrl');
            if (el) {
              el.setWidth(w - 50);
            }
            var gp = Ext.getCmp('getCapsGridPanel');
            if (gp && Ext.getCmp('directions').getResizeEl()) {
              Ext.defer(function() {
                delete gp.anchorSpec;
                gp.anchor = '100% -' + Ext.getCmp('directions').getHeight();
                p.doLayout();
              },100);
            }
          }
        }
      }
      ,{
         region   : 'center'
        ,split    : true
        ,layout   : 'column'
        ,onResize : function() {
          Ext.Panel.prototype.onResize.apply(this,arguments);
          this.items.each(function(i) {
            if (i.rendered) {
              i.setHeight(this.body.getHeight(true));
            }
          },this);
        }
        ,defaults : {border : false}
        ,items    : [
          {
             columnWidth : 0.5
            ,layout      : 'anchor'
            ,defaults    : {border : false}
            ,items       : [
              new Ext.grid.EditorGridPanel({
                store : new Ext.data.ArrayStore({
                   fields    : layerFields
                  ,listeners : {
                    add : function(sto) {
                      var layers = map.layers;
                      for (var i = 0; i < layers.length; i++) {
                        var idx = sto.findExact('name',layers[i].name);
                        if (idx >= 0) {
                          map.setLayerIndex(layers[i],layers.length - 1 - idx);
                        }
                      }
                      refreshTimeline();
                    }
                    ,remove : function(store,rec,idx) {
                      refreshTimeline();
                    }
                  }
                })
                ,columns  : [
                  {dataIndex : 'visible',xtype : 'checkcolumn',width : 25}
                 ,{width : 24,align : 'center',renderer : function(val,metadata,rec) {
                    metadata.attr = 'ext:qtip="Zoom-to layer"';
                    return '<img "margin-top:1px" src="img/globe.png">';
                  }}
                 ,{width : 24,align : 'center',renderer : function(val,metadata,rec) {
                    metadata.attr = 'ext:qtip="Remove layer"';
                    return '<img "margin-top:1px" src="img/delete.png">';
                  }}
                  ,{id : 'color',dataIndex : 'color',width : 24,align : 'center',renderer : function(val,metadata,rec) {
                    if (!rec.get('loaded')) {
                      metadata.attr = 'ext:qtip="Loading..."';
                      return '<img src="img/loading.gif">';
                    }
                    else {
                      metadata.attr = 'ext:qtip="' + (rec.get('color') ? 'Timeline color' : 'This layer has no recognized time capabilities') + '"';
                      return '<div style="border:1px solid #ccc;padding:1px"><div style="width:10px;height:10px;background-color:' + rec.get('color') + ';overflow:hidden"></div></div>';
                    }
                  }}
                  ,{id : 'name',dataIndex : 'name',header : 'Name',renderer : function(val,metadata,rec) {
                    var name = val.split('|');
                    name.pop();
                    return name.join('|');
                  }}
                  ,{
                     dataIndex : 'style'
                    ,header    : 'Style'
                    ,width     : 185
                    ,renderer  : function(val,metadata,rec) {
                      var lyr = map.getLayersByName(rec.get('name'))[0];
                      if (lyr) {
                        return OpenLayers.Util.getParameters(lyr.getFullRequestString({}))['STYLES'];
                      }
                      return val;
                    }
                    ,editor    : new Ext.form.ComboBox({
                      store          : new Ext.data.ArrayStore({
                         fields : ['name','title','layerName']
                      })
                      ,width          : 175
                      ,listWidth      : 250
                      ,triggerAction  : 'all'
                      ,mode           : 'local'
                      ,displayField   : 'name'
                      ,valueField     : 'name'
                      ,listeners      : {
                        select : function(combo,rec,idx) {
                          var lyr = map.getLayersByName(combo.layerName)[0];
                          if (lyr) {
                            lyr.mergeNewParams({STYLES : combo.getValue()});
                          }
                          refreshTimeline();
                        }
                        ,change : function(field,newVal,oldVal) {
                          var lyr = map.getLayersByName(field.layerName)[0];
                          if (lyr) {
                            lyr.mergeNewParams({STYLES : newVal});
                          }
                          refreshTimeline();
                        }
                      }
                    })
                  }
                  ,{dataIndex : 'legend',width : 24,align : 'center',renderer : function(val,metadata,rec) {
                    if (!val) {
                      metadata.attr = 'ext:qtip="Please wait until the layer has rendered before viewing its legend."';
                      return '<img src="img/legend.png">';
                    }
                    else {
                      metadata.attr = 'ext:qtip="<img src=\'' + val + '\'>"';
                      return '<a target=_blank href="' + val + '"><img src="img/legend.png"></a>';
                    }
                  }}
                ]
                ,viewConfig : {
                  markDirty : false
                }
                ,autoExpandColumn : 'name'
                ,selModel         : new Ext.grid.RowSelectionModel({singleSelect:true})
                ,enableDragDrop   : true
                ,enableHdMenu     : false
                ,id               : 'layersGridPanel'
                ,cls              : 'borderRight'
                ,plugins          : [new Ext.ux.dd.GridDragDropRowOrder()]
                ,anchor           : '100% -125'
                ,listeners        : {
                  cellclick : function(g,rowIdx,colIdx) {
                    if (colIdx == 0) {
                      // not sure why I have to do this
                      var rec = g.getStore().getAt(rowIdx);
                      var visible = !rec.get('visible');
                      rec.set('visible',visible);
                      g.getView().refresh();
                      var lyr = map.getLayersByName(rec.get('name'))[0];
                      if (lyr) {
                        lyr.setVisibility(visible);
                      }
                    }
                    else if (colIdx == 1) {
                      var bbox = g.getStore().getAt(rowIdx).get('bbox');
                      map.zoomToExtent(new OpenLayers.Bounds(bbox[0],bbox[1],bbox[2],bbox[3]));
                    }
                    else if (colIdx == 2) {
                      var lyr = map.getLayersByName(g.getStore().getAt(rowIdx).get('name'))[0];
                      if (lyr) {
                        map.removeLayer(lyr);
                      }
                      Ext.getCmp('layersGridPanel').getStore().remove(g.getStore().getAt(rowIdx));
                    }
                  }
                  ,beforeedit : function(e) {
                    this.colModel.getCellEditor(e.column,e.row).field.layerName = e.grid.getStore().getAt(e.row).get('name');
                    var sto = this.colModel.getCellEditor(e.column,e.row).field.getStore();
                    sto.removeAll();
                    var styles = originalStyles[e.grid.getStore().getAt(e.row).get('name')] ? originalStyles[e.grid.getStore().getAt(e.row).get('name')] : e.grid.getStore().getAt(e.row).get('styles');
                    if (!originalStyles[e.grid.getStore().getAt(e.row).get('name')]) {
                      originalStyles[e.grid.getStore().getAt(e.row).get('name')] = styles;
                    }
                    for (var i = 0; i < styles.length; i++) {
                      sto.add(new sto.recordType({
                         name      : styles[i][0]
                        ,title     : styles[i][1]
                      }));
                    }
                  }
                }
                ,tbar             : [
                  {text : 'Active map layers (drag & drop to rerder)'}
                  ,'->'
                  ,{
                     text    : 'Clear'
                    ,handler : function() {
                      var sto = Ext.getCmp('layersGridPanel').getStore();
                      sto.each(function(rec) {
                        var lyr = map.getLayersByName(rec.get('name'))[0];
                        if (lyr) {
                          map.removeLayer(lyr);
                        }
                        sto.remove(rec);
                      });
                      timelineColorCounter = 0;
                    }
                  }
                  ,' '
                ]
              })
              ,{
                 anchor    : '100%'
                ,height    : 125
                ,id        : 'timelineCmp'
                ,cls       : 'borderRight borderTop'
                ,html      : '<div id="timeline"><div class="noTimes"><table><tr><td>Your timeline is empty.<br><br>Either you have not added anything to the active map layers list above,<br>or none of them has any recognized time capabilities.</td></tr></table></div></div>'
                ,listeners : {bodyresize : function(p,w,h) {
                  refreshTimeline();
                }}

              }
            ]
          }
          ,{
             columnWidth : 0.5
            ,layout      : 'anchor'
            ,defaults    : {border : false}
            ,items       : [
              {
                 html       : '<div id="map" style="width:100%;height:100%"></div>'
                ,anchor     : '100% -125'
                ,listeners  : {
                   afterrender : function() {initMap()}
                  ,bodyresize  : function(p,w,h) {
                    var el = document.getElementById('map');
                    if (el) {
                      el.style.width  = w;
                      el.style.height = h;
                      map.updateSize();
                    }
                  }
                }
              }
              ,{
                 anchor    : '100%'
                ,height    : 125
                ,cls       : 'borderTop'
                ,bodyStyle : 'padding-top:6px'
                ,layout    : {type : 'vbox',align : 'center'}
                ,defaults  : {border : false}
                ,items     : [
                  new Ext.FormPanel({
                     buttonAlign : 'center'
                    ,defaults    : {border : false}
                    ,items       : [
                      {bodyStyle : 'padding-left:105px',labelWidth : 75,labelSeparator : '',layout : 'form',items : [
                        new Ext.form.DateField({
                           fieldLabel : 'Map date' 
                          ,id         : 'mapDate'
                          ,emptyText  : 'None set'
                          ,width      : 150
                          ,format     : 'Y-m-d'
                          ,value      : mapDate.format('yyyy-mm-dd')
                        })
                        ,new Ext.form.TimeField({
                           fieldLabel : 'Map time'
                          ,id         : 'mapTime'
                          ,emptyText  : 'None set'
                          ,width      : 150
                          ,format     : 'H:i'
                          ,value      : mapDate.format('HH:MM')
                        })
                      ]}
                      ,{border : false,cls : 'directionsCenter',html : 'Click <b>Apply</b> after making a selection.<br>Seeing pink? Select a specific time point on the timeline.'}
                    ]
                    ,buttons : [
                       {text : '-24h',width : 50,handler : function() {applyDeltaTime(3600000 * -24)}}
                      ,{text : '-12h',width : 50,handler : function() {applyDeltaTime(3600000 * -12)}}
                      ,{text : '-6h',width : 50,handler : function() {applyDeltaTime(3600000 * -6)}}
                      ,' '
                      ,{
                         text    : '<b>Apply</b>'
                        ,width   : 75
                        ,handler : function() {
                          var d = Ext.getCmp('mapDate').getValue();
                          var t = Ext.getCmp('mapTime').getValue().split(':');
                          if (d && t.length == 2) {
                            mapDate = new Date(d.getTime() + t[0] * 3600000 + t[1] * 60000);
                            refreshTimeline();
                            syncMapDateWithLayers();
                          }
                          else {
                            Ext.Msg.alert('Set map date and time',"Oops, it looks like your target map date and time was inavlid.  Please try again.");
                          }
                        }
                      }
                      ,' '
                      ,{text : '+6h',width : 50,handler : function() {applyDeltaTime(3600000 * 6)}}
                      ,{text : '+12h',width : 50,handler : function() {applyDeltaTime(3600000 * 12)}}
                      ,{text : '+24h',width : 50,handler : function() {applyDeltaTime(3600000 * 24)}}
                    ]
                  })
                ]
              }
            ]
          }
        ]
      }
    ]
    ,listeners : {afterrender : function() {
      var u = initUrl ? initUrl : 'http://wms.testbed.sura.org/wms/in_usf_fvcom_ike_ultralite_vardrag_nowave_2d/?service=WMS&version=1.3.0&request=GetCapabilities';
      Ext.getCmp('gcUrl').setValue(u);
      wmsGetCaps(u);
    }}
  });
  checkNewGetCaps();
}

function initMap() {
  var lyr = new OpenLayers.Layer.XYZ(
     'ESRI Ocean'
    ,'http://services.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/${z}/${y}/${x}.jpg'
    ,{
       sphericalMercator : true
      ,isBaseLayer       : true
    }
  );

  map = new OpenLayers.Map('map',{
     layers            : [lyr]
    ,projection        : proj3857
    ,displayProjection : proj4326
    ,units             : 'm'
    ,maxExtent         : new OpenLayers.Bounds(-20037508,-20037508,20037508,20037508.34)
  });
  map.setCenter(new OpenLayers.LonLat(0,0).transform(proj4326,proj3857),0);
}

function wmsGetCaps(url) {
  function wmsGetCapsCallback(sto,r) {
    var caps = new OpenLayers.Format.WMSCapabilities().read(r.responseText);
    if (
      !caps 
      || !caps.capability 
      || !caps.capability.request 
      || !caps.capability.request.getmap 
      || !caps.capability.request.getmap.formats 
      || !caps.capability.request.getmap.href
      || !caps.capability.exception
      || !caps.capability.exception.formats
    ) {
      Ext.Msg.alert('WMS exception','There was an error querying this data service.');
      sto.fireEvent('load');
      return;
    }
    var recs = [];
    for (var i = 0; i < caps.capability.layers.length; i++) {
      var bbox = caps.capability.layers[i].llbbox;
      if (bbox[0] == bbox[2]) {
        bbox[0] -= 5;
        bbox[2] += 5;
      }
      if (bbox[1] == bbox[3]) {
        bbox[1] -= 5;
        bbox[3] += 5;
      }
      var dw = bbox[2] - bbox[0];
      var dh = bbox[3] - bbox[1];
      var w  = 128;
      var h  = w / dw * dh;
      if (h > 128) {
        h = 128;
        w = dw / dh * h;
      }
      w = Math.round(w);
      h = Math.round(h);
      if (isNaN(w) || isNaN(h)) {
        w = 128;
        h = 128;
      }
      var bboxGeo = new OpenLayers.Bounds(bbox[0],bbox[1],bbox[2],bbox[3]).transform(proj4326,proj3857);
      var bgU = OpenLayers.Util.urlAppend(
         'http://nowcoast.noaa.gov/wms/com.esri.wms.Esrimap/geolinks'
        ,OpenLayers.Util.getParameterString({
           SERVICE     : 'WMS'
          ,REQUEST     : 'GetMap'
          ,VERSION     : '1.1.1'
          ,SRS         : 'EPSG:3857'
          ,LAYERS      : 'world_countries,world_rivers,world_lakes,great_lakes'
          ,STYLES      : ''
          ,BBOX        : bboxGeo.toBBOX()
          ,WIDTH       : w
          ,HEIGHT      : h
          ,FORMAT      : 'image/png'
          ,EXCEPTIONS  : 'application/vnd.ogc.se_inimage'
        })
      );

      var getMapU = OpenLayers.Util.urlAppend(
         caps.capability.request.getmap.href 
        ,OpenLayers.Util.getParameterString({
           SERVICE         : 'WMS'
          ,REQUEST         : 'GetMap'
          ,VERSION         : '1.1.1'
          ,SRS             : 'EPSG:3857'
          ,LAYERS          : caps.capability.layers[i].name
          ,STYLES          : caps.capability.layers[i].styles && caps.capability.layers[i].styles[0] ? caps.capability.layers[i].styles[0].name : ''
          ,BBOX            : bboxGeo.toBBOX()
          ,WIDTH           : w
          ,HEIGHT          : h
          ,FORMAT          : caps.capability.request.getmap.formats[0]
          ,EXCEPTIONS      : caps.capability.exception.formats[0]
          ,TRANSPARENT     : true
          ,COLORSCALERANGE : 'auto' // hack for tds
        })
      );
      var thumbU = OpenLayers.Util.urlAppend(
         'getThumb.php'
        ,OpenLayers.Util.getParameterString({
           getCapsUrl : url
          ,getMapUrl  : getMapU
          ,name       : caps.capability.layers[i].name
          ,format     : caps.capability.request.getmap.formats[0]
        })
      );
      var name = caps.capability.layers[i].title;
      if (typeof(name) == 'string') {
        name += ' (' + caps.capability.layers[i].name + ')';
      }
      else {
        name = caps.capability.layers[i].name
      }
      var d = [];
      if (caps.capability.layers[i].dimensions && caps.capability.layers[i].dimensions.time && caps.capability.layers[i].dimensions.time.values) {
        for (var j = 0; j < caps.capability.layers[i].dimensions.time.values.length; j++) {
          var p = caps.capability.layers[i].dimensions.time.values[j].split('/');
          if (p.length >= 2) {
            // We're ignoring anything after date0/date1.
            d.push([isoDateToDate(p[0]),isoDateToDate(p[1])]);
          }
          else {
            d.push(isoDateToDate(caps.capability.layers[i].dimensions.time.values[j]));
          }
        }
      }
      var styles = [];
      if (caps.capability.layers[i].styles) {
        for (var j = 0; j < caps.capability.layers[i].styles.length; j++) {
          styles.push([caps.capability.layers[i].styles[j].name,caps.capability.layers[i].styles[j].title]);
        }
      }
      recs.push(new sto.recordType({
         name     : name + '|' + Ext.id()
        ,getMap   : {
           url    : caps.capability.request.getmap.href
          ,name   : caps.capability.layers[i].name
          ,format : caps.capability.request.getmap.formats[0]
        }
        ,thumb    : thumbU
        ,bg       : bgU
        ,w        : w
        ,h        : h
        ,abstract : caps.capability.layers[i].abstract
        ,okDates  : d
        ,style    : caps.capability.layers[i].styles && caps.capability.layers[i].styles[0] ? caps.capability.layers[i].styles[0].name : ''
        ,styles   : styles
        ,bbox     : bboxGeo.toArray()
        ,visible  : true
        ,loaded   : false
      }));
    }
    sto.add(recs);
    sto.sort('name','ASC');
    sto.fireEvent('load');
  }

  var sto = Ext.getCmp('getCapsGridPanel').getStore();
  sto.removeAll();
  sto.fireEvent('beforeload');
  OpenLayers.Request.issue({
     url      : 'getCaps.php?u=' + encodeURIComponent(url)
    ,callback : OpenLayers.Function.bind(wmsGetCapsCallback,null,sto)
  });
}

function sosGetCaps(url) {
  Ext.Msg.alert('SOS',"I'm sorry, but I don't know how to process this service.");
  return;
  function sosGetCapsCallback(sto,r) {
    var caps = new OpenLayers.Format.SOSCapabilities().read(r.responseText);
    var recs = [];
    sto.add(recs);
    sto.sort('name','ASC');
    sto.fireEvent('load');
  }

  var sto = Ext.getCmp('getCapsGridPanel').getStore();
  sto.removeAll();
  sto.fireEvent('beforeload');
  OpenLayers.Request.issue({
     url      : 'getCaps.php?u=' + encodeURIComponent(url)
    ,callback : OpenLayers.Function.bind(sosGetCapsCallback,null,sto)
  });
}

function isoDateToDate(s) {
  // 2010-01-01T00:00:00Z
  s = s.replace("\n",'');
  var p = s.split('T');
  if (p.length == 2) {
    var ymd = p[0].split('-');
    var hm = p[1].split(':');
    return new Date(
       ymd[0]
      ,ymd[1] - 1
      ,ymd[2]
      ,hm[0]
      ,hm[1]
    );
  }
  else {
    return false;
  }
}

function refreshTimeline() {
  var tl = document.getElementById('timeline');
  if (!tl) {
    return;
  }
  var recData = [];
  Ext.getCmp('layersGridPanel').getStore().each(function(rec) {
    var dates      = rec.get('okDates');
    var pointsOnly = false;
    if (dates) {
      var data = [];
      for (var i = 0; i < dates.length; i++) {
        if (dates[i].length == 2) {
          data.push([dates[i][0].getTime(),-recData.length]);
          data.push([dates[i][1].getTime(),-recData.length]);
        }
        else {
          data.push([dates[i].getTime(),-recData.length]);
          pointsOnly = true;
        }
      }
      if (data.length > 0) {
        var label = rec.get('name').split('|');
        label.pop();
        recData.push({
           data      : data
          ,points    : {show : true}
          ,lines     : {show : !pointsOnly}
          ,color     : rec.get('color')
          ,label     : label.join('|')
        });
      }
    }
  });
  if (recData.length > 0) {
    tl.style.width  = Ext.getCmp('timelineCmp').getSize(true).width + 'px';
    tl.style.height = Ext.getCmp('timelineCmp').getSize(true).height + 'px';
    $('#timeline').empty();
    timelinePlot = $.plot(
       $('#timeline')
      ,recData
      ,{
         xaxis     : {mode  : 'time',timeformat : "%b %d<br>%y"}
        ,yaxis     : {ticks : false,min : -recData.length,max : 1}
        ,crosshair : {mode  : 'x'}
        ,legend    : {show : false}
        ,grid      : {
           backgroundColor : {colors : ['#fff','#eee']}
          ,borderWidth     : 1
          ,borderColor     : '#99BBE8'
          ,hoverable       : true
          ,clickable       : true
          ,markings        : function() {
            return mapDate ? [{color : '#00b',xaxis : {from : mapDate.getTime(),to : mapDate.getTime()}}] : []
          }
        }
      }
    );
    if (!timelinePlotInit) {
      $('#timeline').bind('plothover',function(e,pos,item) {
        $("#tooltip").remove();
        var d = new Date(pos.x).format('isoDateTime');
        var txt = '';
        if (item) {
          d   = new Date(item.datapoint[0]).format('isoDateTime');
          txt = ' : <b>specific time</b><br>' + item.series.label;
        }
        $("<div id='tooltip'>" + d + txt + "</div>").css({
           position           : "absolute"
          ,display            : "none"
          ,top                : pos.pageY - 15
          ,left               : pos.pageX + 10
          ,border             : "1px solid #99BBE8"
          ,padding            : "3px"
          ,"background-color" : "#fff"
          ,opacity            : 1
          ,font               : '12px tahoma,helvetica,sans-serif'
          ,"z-index"          : 1000
        }).appendTo("body").fadeIn(0);
      });
      $('#timeline').bind('plotclick',function(e,pos,item) {
        var d = new Date(pos.x);
        if (item) {
          d = new Date(item.datapoint[0]);
        }
        mapDate = d;
        Ext.getCmp('mapDate').setValue(mapDate.format('yyyy-mm-dd'));
        Ext.getCmp('mapTime').setValue(mapDate.format('HH:MM'));
        syncMapDateWithLayers();
        timelinePlot.draw();
      });
      $('#timeline').bind('mouseout',function() {
        $("#tooltip").remove();
      });
    }
    timelinePlotInit = true;
  }
  else {
    document.getElementById('timeline').innerHTML = '<div class="noTimes"><table><tr><td>Your timeline is empty.<br><br>Either you have not added anything to the active map layers list above,<br>or none of them has any recognized time capabilities.</td></tr></table></div>';
  }
}

function applyDeltaTime(ms) {
  if (!mapDate) {
    Ext.Msg.alert('Set map date and time',"Oops, it looks like your target map date and time was inavlid.  Please try again.");
  }
  else {
    mapDate = new Date(mapDate.getTime() + ms);
    Ext.getCmp('mapDate').setValue(mapDate.format('yyyy-mm-dd'));
    Ext.getCmp('mapTime').setValue(mapDate.format('HH:MM'));
    refreshTimeline();
    syncMapDateWithLayers();
  }
}

function syncMapDateWithLayers() {
  Ext.getCmp('layersGridPanel').getStore().each(function(rec) {
    var lyr = map.getLayersByName(rec.get('name'))[0];
    if (lyr) {
      lyr.mergeNewParams({TIME : mapDate.format("yyyy-mm-dd'T'HH:MM")});
    }
  });
}

function addToMap(name) {
  var rec = Ext.getCmp('getCapsGridPanel').getStore().getAt(Ext.getCmp('getCapsGridPanel').getStore().findExact('name',name));
  var sto = Ext.getCmp('layersGridPanel').getStore();
  if (sto.findExact('name',rec.get('name')) >= 0) {
    Ext.Msg.alert('Add layer',"Sorry, but this layer has already been added to your map.");
    return;
  }
  var dates = rec.get('okDates');
  var maxD;
  if (dates.length > 0) {
    rec.set('color',timelineColors[timelineColorCounter++ % timelineColors.length]);
    if (sto.getCount() == 0) {
      for (var i = 0; i < dates.length; i++) {
        if (dates[i].length == 2) {
          if (!maxD || maxD < dates[i][1]) {
            maxD = dates[i][1];
          }
        }
        else if (!maxD || maxD < dates[i]) {
          maxD = dates[i];
        }
      }
    }
    if (maxD) {
      mapDate = maxD;
      Ext.getCmp('mapDate').setValue(mapDate.format('yyyy-mm-dd'));
      Ext.getCmp('mapTime').setValue(mapDate.format('HH:MM'));
    }
  }
  sto.insert(0,[rec]);
  var bbox = rec.get('bbox');
  if (sto.getCount() == 1 || !map.getExtent().intersectsBounds(new OpenLayers.Bounds(bbox[0],bbox[1],bbox[2],bbox[3]))) {
    map.zoomToExtent(new OpenLayers.Bounds(bbox[0],bbox[1],bbox[2],bbox[3]));
  }
  var lyr = new OpenLayers.Layer.WMS(
     rec.get('name')
    ,rec.get('getMap').url
    ,{
       layers          : rec.get('getMap').name
      ,transparent     : true
      ,styles          : rec.get('style')
      ,format          : rec.get('getMap').format
      ,projection      : proj3857
    }
    ,{
       isBaseLayer      : false
      ,singleTile       : true
      ,visibility       : true
      ,opacity          : 1
      ,noMagic          : true
      ,transitionEffect : 'resize'
    }
  );
  if (mapDate) {
    lyr.mergeNewParams({TIME : mapDate.format("yyyy-mm-dd'T'HH:MM")});
  }
  lyr.mergeNewParams({COLORSCALERANGE : 'auto'}); // hack for tds
  lyr.events.register('loadstart',this,function(e) {
    var sto = Ext.getCmp('layersGridPanel').getStore();
    var idx = sto.findExact('name',e.object.name);
    if (idx >= 0) {
      var rec = sto.getAt(idx);
      rec.set('loaded',false);
      var p = OpenLayers.Util.getParameters(e.object.getFullRequestString({}));
      var params = {
         REQUEST : 'GetLegendGraphic'
        ,LAYER   : p['LAYERS']
      };
      var img = new Image();
      img.src = e.object.getFullRequestString(params);
      rec.set('legend',e.object.getFullRequestString(params));
      rec.commit();
    }
    Ext.getCmp('layersGridPanel').getView().refresh();
  });

  lyr.events.register('loadend',this,function(e) {
    var sto = Ext.getCmp('layersGridPanel').getStore();
    var idx = sto.findExact('name',e.object.name);
    if (idx >= 0) {
      var rec = sto.getAt(idx);
      rec.set('loaded',true);
      rec.commit();
    }
    Ext.getCmp('layersGridPanel').getView().refresh();
  });
  map.addLayer(lyr);
}

function checkNewGetCaps() {
  var u = Ext.util.Cookies.get('getCapsUrl');
  Ext.util.Cookies.set('getCapsUrl','');
  if (new RegExp(/^http/i).test(u)) {
    Ext.getCmp('gcUrl').setValue(u);
    wmsGetCaps(u);
  }
  setTimeout('checkNewGetCaps()',100);
}
